// 纬度转换
import useToast from '@/hooks/useToast';

function transformLat(lng: number, lat: number) {
  let ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng));
  ret += ((20.0 * Math.sin(6.0 * lng * Math.PI) + 20.0 * Math.sin(2.0 * lng * Math.PI)) * 2.0) / 3.0;
  ret += ((20.0 * Math.sin(lat * Math.PI) + 40.0 * Math.sin((lat / 3.0) * Math.PI)) * 2.0) / 3.0;
  ret += ((160.0 * Math.sin((lat / 12.0) * Math.PI) + 320 * Math.sin((lat * Math.PI) / 30.0)) * 2.0) / 3.0;
  return ret;
}

// 经度转换
function transformLng(lng: number, lat: number) {
  let ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng));
  ret += ((20.0 * Math.sin(6.0 * lng * Math.PI) + 20.0 * Math.sin(2.0 * lng * Math.PI)) * 2.0) / 3.0;
  ret += ((20.0 * Math.sin(lng * Math.PI) + 40.0 * Math.sin((lng / 3.0) * Math.PI)) * 2.0) / 3.0;
  ret += ((150.0 * Math.sin((lng / 12.0) * Math.PI) + 300.0 * Math.sin((lng / 30.0) * Math.PI)) * 2.0) / 3.0;
  return ret;
}

// 判断是否在中国境内
function outOfChina(lng: number, lat: number) {
  return !(lng > 73.66 && lng < 135.05 && lat > 3.86 && lat < 53.55);
}

interface ICoordinate {
  lng: number;
  lat: number;
}

export interface IShoppingCartDiscountResult {
  sku: string;
  name: string;
  goodsId: number;
  variantId: string;
  activeCount: number;
  activePrice: number;
  salePriceCount: number;
  salePrice: number;
  maxNum: number;
}

export interface ImageInfo {
  path: string;
  width: number;
  height: number;
  type: string;
  orientation?: string;
}

const { showToast } = useToast();
class Tools {
  /**
   * 获取微信登录凭证（code）
   */
  public static async getWechatLoginCode(): Promise<string> {
    return new Promise((resolve, reject) => {
      uni.login({
        provider: 'weixin',
        success: (loginRes) => {
          resolve(loginRes.code);
        },
        fail: (error) => {
          reject(error);
        },
      });
    });
  }

  /**
   * 拨打电话
   * @param phoneNumber
   */
  public static async callPhone(phoneNumber: string) {
    await uni.makePhoneCall({
      phoneNumber,
    });
  }

  /**
   * 获得图片信息
   * @param src
   */
  public static async getImageInfo(src: string): Promise<ImageInfo> {
    return new Promise((resolve, reject) => {
      if (!src) {
        reject(new Error('图片地址不能为空'));
        return;
      }
      uni.getImageInfo({
        src,
        success: (res) => {
          resolve(res as ImageInfo);
        },
        fail: (err) => {
          reject(err);
        },
      });
    });
  }

  /**
   * 将微信小程序临时文件转为 Base64
   * @param filePath 微信临时文件路径
   * @returns Promise<string> 返回文件的 Base64 字符串
   */
  public static async uniBase64FromFile(filePath: string): Promise<string> {
    return new Promise((resolve, reject) => {
      uni.getFileSystemManager().readFile({
        filePath,
        encoding: 'base64',
        success: (res) => {
          resolve(`data:image/jpeg;base64,${res.data}`);
        },
        fail: (err) => {
          reject(err);
        },
      });
    });
  }

  /**
   * 将 Base64 数据转换为图片文件并保存到临时路径
   * @param base64 - Base64 字符串
   * @param fileName - 保存的文件名，例如 "image.png"
   * @returns Promise<string> - 返回保存图片的临时路径
   */
  public static base64ToImage(base64: string, fileName: string): Promise<string> {
    return new Promise((resolve, reject) => {
      const base64Regex = /^data:image\/(\w+);base64,/;
      const match = base64.match(base64Regex);
      if (!match) {
        showToast('base64文件格式错误');
        return reject();
      }
      const fileType = match[1];
      const base64Data = base64.replace(base64Regex, '');
      const buffer = uni.base64ToArrayBuffer(base64Data);
      // 保存文件到本地临时路径
      const userDataPath = wx.env.USER_DATA_PATH;
      const filePath = `${userDataPath}/${fileName}.${fileType}`;
      uni.getFileSystemManager().writeFile({
        filePath,
        data: buffer,
        encoding: 'binary',
        success: () => resolve(filePath),
        fail: (err) => reject(err),
      });
    });
  }

  /**
   * 序列化url地址参数
   * @param url
   */
  public static parseUrlParams(url: string): Record<string, string> {
    const params: Record<string, string> = {};
    const queryString = url.split('?')[1];

    if (queryString) {
      const pairs = queryString.split('&');

      pairs.forEach((pair) => {
        const [key, value] = pair.split('=');
        if (key && value) {
          params[decodeURIComponent(key)] = decodeURIComponent(value);
        }
      });
    }

    return params;
  }

  /**
   * 保护手机号隐私
   * @param phone
   */
  public static ProtectingPhoneNumbers(phone: string) {
    return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
  }

  /**
   * WGS-84 转换为 GCJ-02
   * @param lng
   * @param lat
   */
  public static wgs84ToGcj02(lng: number, lat: number) {
    const pi = 3.1415926535897932384626;
    const a = 6378245.0; // WGS 长半轴
    const ee = 0.00669342162296594323; // WGS 偏心率的平方
    if (outOfChina(lng, lat)) {
      return [lng, lat];
    }
    let dlat = transformLat(lng - 105.0, lat - 35.0);
    let dlng = transformLng(lng - 105.0, lat - 35.0);
    const radLat = (lat / 180.0) * pi;
    let magic = Math.sin(radLat);
    magic = 1 - ee * magic * magic;
    const sqrtMagic = Math.sqrt(magic);
    dlat = (dlat * 180.0) / (((a * (1 - ee)) / (magic * sqrtMagic)) * pi);
    dlng = (dlng * 180.0) / ((a / sqrtMagic) * Math.cos(radLat) * pi);
    const mgLat = lat + dlat;
    const mgLng = lng + dlng;
    return [mgLng, mgLat];
  }

  /**
   * GCJ-02 转换为 BD-09
   * @param lng
   * @param lat
   */
  public static gcj02ToBd09(lng: number, lat: number) {
    const x = lng,
      y = lat;
    const z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * Math.PI);
    const theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * Math.PI);
    const bd_lng = z * Math.cos(theta) + 0.0065;
    const bd_lat = z * Math.sin(theta) + 0.006;
    return [bd_lng, bd_lat];
  }

  /**
   * GPS坐标转百度坐标
   * @param lng
   * @param lat
   */
  public static gpsCoordinateToBaiduCoordinate(lng: number, lat: number): ICoordinate {
    const gcj02Coords = this.wgs84ToGcj02(lng, lat); // WGS-84 转 GCJ-02
    const bd09Coords = this.gcj02ToBd09(gcj02Coords[0], gcj02Coords[1]); // GCJ-02 转 BD-09
    return { lng: bd09Coords[0], lat: bd09Coords[1] };
  }

  /**
   * 腾讯坐标转百度坐标
   * @param lng
   * @param lat
   */
  public static tencentCoordinateToBaiduCoordinate(lng: number, lat: number): ICoordinate {
    const x_pi = (3.14159265358979324 * 3000.0) / 180.0;
    const x = lng;
    const y = lat;
    const z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * x_pi);
    const theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * x_pi);
    const lngs = z * Math.cos(theta) + 0.0065;
    const lats = z * Math.sin(theta) + 0.006;
    return { lng: lngs, lat: lats };
  }

  /**
   * 百度坐标转腾讯坐标
   * @param lng
   * @param lat
   */
  public static baiduCoordinateToTencentCoordinate(lng: number, lat: number): ICoordinate {
    const x_pi = (3.14159265358979324 * 3000.0) / 180.0;
    const x = lng - 0.0065;
    const y = lat - 0.006;
    const z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_pi);
    const theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_pi);
    const lngs = z * Math.cos(theta);
    const lats = z * Math.sin(theta);

    return { lng: lngs, lat: lats };
  }

  /**
   * 根据id查找匹配项
   * @param nodes
   * @param id
   */
  public static findTreeNodeById(nodes: any[], id: number) {
    for (const node of nodes) {
      if (node.id === id) {
        return node;
      }

      if (node.children) {
        const result: any = this.findTreeNodeById(node.children, id);
        if (result) {
          return result;
        }
      }
    }

    return undefined;
  }

  /**
   * 将数组根据size转换成二维数组
   * @param arr
   * @param size
   */
  public static convertOneDToTwoD<T>(arr: T[], size: number) {
    let tempArr: any[] = [];
    const length = arr.length;
    let index = 0;
    if (length <= size) return [arr];
    let arrNum = Math.ceil(length / size);
    for (let i = 0; i < arrNum; i++) {
      tempArr[i] = [];
      let end = index + size < length ? index + size : length;
      let tempIndex = index;
      for (let j = tempIndex; j < end; j++) {
        tempArr[i].push(arr[j]);
        index++;
      }
    }
    return tempArr;
  }

  /**
   * 深拷贝
   * @param obj
   */
  public static deepClone<T>(obj: T): T {
    // 检查是否为null或不是对象类型
    if (obj === null || typeof obj !== 'object') {
      return obj;
    }

    // 处理日期对象
    if (obj instanceof Date) {
      return new Date(obj) as T;
    }

    // 处理数组
    if (Array.isArray(obj)) {
      const copy: any[] = [];
      for (let i = 0; i < obj.length; i++) {
        copy[i] = this.deepClone(obj[i]);
      }
      return copy as T;
    }

    // 处理对象
    if (obj instanceof Object) {
      const copy: { [key: string]: any } = {};
      for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
          copy[key] = this.deepClone(obj[key]);
        }
      }
      return copy as T;
    }

    throw new Error('无法复制该数据类型');
  }

  /**
   * 将毫秒转换成分秒中文
   * @param milliseconds
   */
  public static millisecondConversionTimeFormat(milliseconds: number): string {
    const totalSeconds = Math.floor(milliseconds / 1000);
    if (totalSeconds < 60) {
      return `${totalSeconds}秒`;
    } else {
      const minutes = Math.floor(totalSeconds / 60);
      const seconds = totalSeconds % 60;
      return `${minutes}分${seconds}秒`;
    }
  }

  /**
   * 将富文本中文字提炼出来
   * @param html
   */
  public static extractChineseTextFromHtml(html: string): string[] {
    const chineseTextRegex = />([^<>]*[\u4e00-\u9fa5]+[^<>]*)</g;
    const result: string[] = [];
    let match;

    while ((match = chineseTextRegex.exec(html)) !== null) {
      const text = match[1].trim();
      if (text) {
        result.push(text);
      }
    }

    return result;
  }

  /**
   * 购物车中是否有该产品
   * @param shoppingCart
   * @param product
   */
  public static hasValueInShoppingCart(shoppingCart: IProductInformation[], product: IProductInformation): number {
    if (shoppingCart.find((item) => item.id === product.id)) {
      return shoppingCart.find((item) => item.id === product.id)?.nums || 0;
    } else {
      return 0;
    }
  }

  /**
   * 购物车商品总数
   * @param shoppingCart
   */
  public static getShoppingCartProductQuantity(shoppingCart: IProductInformation[]): number {
    return shoppingCart.reduce((total, item) => total + item.nums, 0);
  }

  /**
   * 提取商户产品接口数据，将其扁平化同时去重复组成产品数组
   * @param products
   */
  public static flatProductData(products: IObtainProductListRes[]) {
    const allProducts = products.flatMap((item) => item.list);

    return allProducts.reduce((acc, current) => {
      if (!acc.some((item) => item.goodsId === current.goodsId)) {
        acc.push(current);
      }
      return acc;
    }, [] as IProductInformation[]);
  }

  /**
   * 判断产品是否在可售时间内
   * @param product
   */
  public static hasProductInSaleTime(product: IProductInformation): boolean {
    if (!product.goodsHours) return true;
    if (product.goodsHours.length === 0) return true;

    const now = new Date();
    const currentMinutes = now.getHours() * 60 + now.getMinutes();

    return product.goodsHours.some(({ beginHours, endHours }) => {
      // 将 beginHours 和 endHours 转为分钟表示
      const [beginHour, beginMinute] = beginHours.split(':').map(Number);
      const [endHour, endMinute] = endHours.split(':').map(Number);
      const beginMinutes = beginHour * 60 + beginMinute;
      const endMinutes = endHour * 60 + endMinute;

      // 检查当前时间是否在此时间段内
      return currentMinutes >= beginMinutes && currentMinutes <= endMinutes;
    });
  }

  /**
   * 渲染产品类型规则、验证是否可售
   * @param product
   * @return 1: 不在可售时间 2: 售罄 3: 规格产品 4: 普通产品可直接加减购物车
   */
  public static renderProductValidation(product: IProductInformation): number {
    if (!this.hasProductInSaleTime(product)) {
      return 1;
    }
    if (product.stock === 0 || product.saleState !== 2) {
      return 2;
    }
    if (product.variantState === 2 || product.goodsType === 2 || product.huskFlag) {
      return 3;
    }
    return 4;
  }

  /**
   * 计算是否处于活动区间(活动时间、购物车是否已达到活动购买上限)
   * @param product
   * @param shoppingCart
   * @param count
   * @param hasSku
   */
  public static hasActiveRange(product: IProductInformation, shoppingCart: IProductInformation[], count: number = 0, hasSku: boolean = false): boolean {
    const { active, goodsId, sku } = product;
    if (!active) return false;

    const { activeId, beginDate, endDate, maxCount, maxNum } = active;
    const now = new Date();

    // 判断是否在活动时间范围内
    const begin = new Date(beginDate);
    const end = new Date(endDate);
    if (now < begin || now > end) {
      return false;
    }

    // 统计购物车中相同 activeId 的产品数量
    let totalActiveCount = count;
    let currentProductCount = count;

    for (const item of shoppingCart) {
      if (item.active && item.active.activeId === activeId) {
        totalActiveCount += item.nums;
        if (hasSku) {
          if (goodsId === item.goodsId && sku === item.sku) {
            currentProductCount = item.nums;
          }
        } else {
          if (goodsId === item.goodsId) {
            currentProductCount = item.nums;
          }
        }
      }
    }

    // 判断是否符合活动的数量限制
    return totalActiveCount < maxCount && currentProductCount < maxNum;
  }

  /**
   * 计算购物车活动产品折扣最优解
   * @param products
   */
  public static calculateShoppingCartDiscount(products: IProductInformation[]): { results: IShoppingCartDiscountResult[]; totalPrice: number; totalDiscountPrice: number } {
    const now = new Date();
    const results: IShoppingCartDiscountResult[] = [];
    let totalPrice = 0;
    let totalDiscountPrice = 0;

    // 筛选出在活动时间内的商品，并根据activeId分组
    const activeGroups: Record<number, IProductInformation[]> = {};

    products.forEach((item) => {
      const { active, nums, salePrice, variantId, sku, goodsId, name } = item;

      if (active) {
        const begin = new Date(active.beginDate);
        const end = new Date(active.endDate);

        if (now >= begin && now <= end) {
          if (!activeGroups[active.activeId]) {
            activeGroups[active.activeId] = [];
          }
          activeGroups[active.activeId].push(item);
        }
      } else {
        // 没有活动直接按原价
        const originalTotal = nums * salePrice;
        results.push({ name, sku: sku || '', goodsId, variantId, activeCount: 0, activePrice: 0, salePriceCount: nums, salePrice: salePrice, maxNum: 0 });
        totalPrice += originalTotal;
      }
    });

    // 处理每个活动组，计算优惠
    Object.values(activeGroups).forEach((group) => {
      const activeInfo = group[0].active!;
      const { maxCount } = activeInfo;
      let remainingActivityCount = maxCount;

      group.sort((a, b) => b.salePrice - b.active!.activePrice - (a.salePrice - a.active!.activePrice));

      group.forEach((item) => {
        const { nums, salePrice, variantId, active, sku, goodsId, name, price } = item;
        const maxNum = active!.maxNum;

        const activeCount = Math.min(nums, maxNum, remainingActivityCount);
        const salePriceCount = nums - activeCount;

        // 计算当前商品的总价和优惠
        const activeTotal = activeCount * active!.activePrice;
        const originalTotal = salePriceCount * salePrice;
        const discount = activeCount * (price - active!.activePrice);

        // 累加总价和总优惠
        totalPrice += activeTotal + originalTotal;
        totalDiscountPrice += discount;

        results.push({ name, sku: sku || '', goodsId, variantId, activeCount, activePrice: active!.activePrice, salePriceCount, salePrice: salePrice, maxNum: active.maxNum });

        remainingActivityCount -= activeCount;
      });
    });

    return { results, totalPrice, totalDiscountPrice };
  }

  /**
   * 删除空的子节点并把每一项的id变为字符串,该函数主要是为了栋单元层组件使用，并且为了
   * 适配cascader需要填写把id变为字符串
   */
  public static deEmptyChildrenCascader<T extends { id: number | string; children?: T[] }>(arr: T[]) {
    arr.forEach((item) => {
      // 将 id 转换为字符串
      item.id = String(item.id);
      // 如果没有 children 属性，直接返回
      if (!item.children) return;
      // 如果 children 是空数组，则删除 children 属性
      if (Array.isArray(item.children) && item.children.length === 0) {
        Reflect.deleteProperty(item, 'children');
        return; // 删除后无需继续处理
      }
      // 递归处理 children
      this.deEmptyChildrenCascader(item.children);
    });
    return arr;
  }

  /**
   * 根据树节点id查找树路径，返回一条完整的树数组
   * @param {Array} tree - 树结构数组
   * @param {string} id - 目标节点ID
   * @returns {Array} - 包含完整路径的数组
   */
  public static findPathDFS<T extends { id: number | string; children?: T[] }>(tree: T[], id: number | string): T | null {
    for (const node of tree) {
      // 检查当前节点的 id 是否匹配
      if (node.id === id) {
        return node; // 找到节点，直接返回
      }

      // 如果当前节点有子节点，则递归查找
      if (node.children) {
        const foundNode = this.findPathDFS(node.children, id);
        if (foundNode) {
          return foundNode; // 如果在子节点中找到，返回该节点
        }
      }
    }
    // 如果没有找到节点，返回 null
    return null;
  }

  /**
   * 时间戳转换时间格式 yyyy-mm-dd
   * @param timestamp
   */
  public static formatTimestampToDate(timestamp: number) {
    // 创建一个 Date 对象
    const date = new Date(timestamp);
    // 获取年份、月份和日期
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份从0开始，所以要加1
    const day = String(date.getDate()).padStart(2, '0');
    // 返回格式化后的日期字符串
    return {
      year,
      month,
      day,
    };
  }

  /**
   * 时间字符串转换时间格式 yyyy-mm-dd
   * @param timeStr
   */
  public static formatTimeStringToDate(timeStr: string) {
    const date = new Date(timeStr);
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份从 0 开始，所以加 1，并确保为两位数
    const day = String(date.getDate()).padStart(2, '0'); // 确保日期为两位数
    return `${year}-${month}-${day}`;
  }

  /**
   * 时间格式化
   * @param timestamp
   * @param format
   */
  public static formatTimestamp(timestamp: number, format: string = 'YYYY-MM-DD hh:mm:ss'): string {
    if (isNaN(timestamp) || timestamp <= 0) {
      throw new Error('时间格式错误');
    }

    const msTimestamp = timestamp.toString().length === 10 ? timestamp * 1000 : timestamp;

    const date = new Date(msTimestamp);

    const tokens: { [key: string]: string } = {
      YYYY: date.getFullYear().toString(), // 年
      MM: (date.getMonth() + 1).toString().padStart(2, '0'), // 月，补零
      DD: date.getDate().toString().padStart(2, '0'), // 日，补零
      hh: date.getHours().toString().padStart(2, '0'), // 小时，补零
      mm: date.getMinutes().toString().padStart(2, '0'), // 分钟，补零
      ss: date.getSeconds().toString().padStart(2, '0'), // 秒，补零
    };

    return format.replace(/YYYY|MM|DD|hh|mm|ss/g, (token) => tokens[token]);
  }
}

export default Tools;
